package com.binance.gateway.filter.limit;

import com.binance.gateway.filter.BaseFilter;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitKeyGenerator;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.RateLimitUtils;
import com.marcosbarbero.cloud.autoconfigure.zuul.ratelimit.config.properties.RateLimitProperties;
import com.netflix.zuul.context.RequestContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.Route;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.web.util.UrlPathHelper;

import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * Created by Fei.Huang on 2018/8/21.
 */
@Log4j2
@RequiredArgsConstructor
public abstract class AbstractRateLimitFilter extends BaseFilter {

    protected static final String TRUE = "true";
    protected static final String OFF = "off";
    protected static final String RATE_LIMIT_EXCEEDED = "rateLimitExceeded";

    protected static final String HIGH_FREQUENCY_USER_LIMIT = "high_frequency_user_limit";
    protected static final String HIGH_FREQUENCY_USER_IGNORE = "high_frequency_user_ignore";
    protected static final String HIGH_FREQUENCY_INTERCEPT_SWITCH = "high_frequency_intercept_switch";

    private final RateLimitProperties properties;
    private final RouteLocator routeLocator;
    private final UrlPathHelper urlPathHelper;
    private final RateLimitUtils rateLimitUtils;
    protected final RateLimitKeyGenerator rateLimitKeyGenerator;

    public String getKey(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
        final String identifier = null != getUserId() ? getUserId().toString() : request.getRequestedSessionId().toLowerCase();
        return String.format("%s:%s", rateLimitKeyGenerator.key(request, route, policy), identifier);
    }

    public String getUserIdUrlKey(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
        final String identifier = null != getUserId() ? getUserId().toString() : request.getRequestedSessionId().toLowerCase();
        return String.format("%s:%s:%s", rateLimitKeyGenerator.key(request, route, policy), identifier, request.getRequestURI());
    }


    @Override
    public boolean shouldFilter() {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();

        // 开关关闭不限制
        if (StringUtils.equalsIgnoreCase(OFF, sysConfigHelper.getCodeByDisplayName(HIGH_FREQUENCY_INTERCEPT_SWITCH))) {
            return false;
        }

        // 未登录不限制
        if (null == super.getUserId(request)) {
            return false;
        }

        // 白名单userId不限制
        if (StringUtils.contains(sysConfigHelper.getCodeByDisplayName(HIGH_FREQUENCY_USER_IGNORE), String.valueOf(super.getUserId(request)))) {
            return false;
        }

        return properties.isEnabled() && !policy(route(request), request).isEmpty();
    }

    protected Route route(HttpServletRequest request) {
        String requestURI = urlPathHelper.getPathWithinApplication(request);
        return routeLocator.getMatchingRoute(requestURI);
    }

    protected List<RateLimitProperties.Policy> policy(Route route, HttpServletRequest request) {
        String routeId = Optional.ofNullable(route).map(Route::getId).orElse(null);
        return properties.getPolicies(routeId).stream()
                .filter(policy -> applyPolicy(request, route, policy))
                .collect(Collectors.toList());
    }

    private boolean applyPolicy(HttpServletRequest request, Route route, RateLimitProperties.Policy policy) {
        List<RateLimitProperties.Policy.MatchType> types = policy.getType();
        return types.isEmpty() || types.stream().allMatch(type -> type.apply(request, route, rateLimitUtils));
    }


}